package com.service.test;

import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.io.IOException;
import java.util.Vector;

public class SendTrapToAliyun2 {
	//格式"udp:172.16.154.99/162" 表示向172.16.154.99这个地址的162端口发送消息
	private String address;

	private String securityName;

	private String authProtocol;

	private String authPassPhrase;

	private String privProtocol;

	private String privPassPhrase;

	//协议的版 一共有三个版本，v1 v2c 和 v3 其中1 2 版本通信的格式相同，仅仅依靠 共同体名字进行校验
	//v3 需要使用上面的6个字段进行身份校验
	private int version;
	//向snmp服务器发送信息的对象，
	private Snmp snmp = null;
	//目标服务，我向那个snmp服务发送消息
	private Target target;

	public SendTrapToAliyun2(String address, String securityName, String authProtocol, String authPassPhrase, String privProtocol, String privPassPhrase, int version) throws IOException {
		this.address = address;
		this.securityName = securityName;
		this.authProtocol = authProtocol;
		this.authPassPhrase = authPassPhrase;
		this.privProtocol = privProtocol;
		this.privPassPhrase = privPassPhrase;
		this.version = version;
		this.init();
	}

	private void init() throws IOException {
		//定义发送端snmp
		Address targetAddress = GenericAddress.parse(address);
		TransportMapping transport;
		if(targetAddress instanceof UdpAddress) {
			transport = new DefaultUdpTransportMapping();
		} else {
			transport = new DefaultTcpTransportMapping();
		}
		snmp = new Snmp(transport);

		//如果是snmpv3版本，走下面的逻辑发消息，如果不是v3，则走v2c的格式发送消息，仅指定共同体名字即可
		if(version == SnmpConstants.version3) {
			//smmpV3的安全机制
			OID authProtocolOID = null;
			if(authProtocol!=null) {
				if(authProtocol.equals("SHA")) {
					authProtocolOID = AuthSHA.ID;
				} else if(authProtocol.endsWith("256")) {
					authProtocolOID = AuthHMAC192SHA256.ID;
				} else if(authProtocol.endsWith("384")) {
					authProtocolOID = AuthHMAC256SHA384.ID;
				} else if(authProtocol.endsWith("512")) {
					authProtocolOID = AuthHMAC384SHA512.ID;
				}
			}
			OID privProtocolOID = null;
			if(privProtocol!=null) {
				if(privProtocol.equals("AES-128")) {
					privProtocolOID = PrivAES128.ID;
				} else if(privProtocol.equals("AES-192")) {
					privProtocolOID = PrivAES192.ID;
				} else if(privProtocol.equals("AES-256")) {
					privProtocolOID = PrivAES256.ID;
				}
			}
			USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
			SecurityModels.getInstance().addSecurityModel(usm);
			snmp.getUSM().addUser(new OctetString(securityName), new UsmUser(new OctetString(securityName), authProtocolOID, new OctetString(authPassPhrase), privProtocolOID, new OctetString(privPassPhrase)));

//			UserTarget userTarget = new UserTarget();
//			if(authProtocol!=null) {
//				if(privProtocol!=null) {
//					target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
//				} else {
//					target.setSecurityLevel(SecurityLevel.AUTH_NOPRIV);
//				}
//			} else {
//				target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
//			}
//			target.setSecurityName(new OctetString(securityName));
			//发送目标
			target = new UserTarget();
			if(authProtocol!=null) {
				if(privProtocol!=null) {
					target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
				} else {
					target.setSecurityLevel(SecurityLevel.AUTH_NOPRIV);
				}
			} else {
				target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
			}
			target.setSecurityName(new OctetString(securityName));
		} else {
			//snmpV2的安全机制
			target = new CommunityTarget();
			((CommunityTarget)target).setCommunity(new OctetString("public"));
		}
		target.setVersion(version);
		target.setAddress(targetAddress);
//		target.setRetries(2);
		target.setTimeout(5*1000);
		transport.listen();
	}

	public void send(String oid) throws IOException {
		//准备要发送的数据单元
		PDU pdu = null;
		if(version == SnmpConstants.version3) {
			pdu = new ScopedPDU();
		} else {
			pdu = new PDU();
		}
		pdu.setType(PDU.GET);   //GET会发送一个get请求，TRAP会发送一个trap命令
		//pdu内部以键值对传递数据
		pdu.add(new VariableBinding(new OID(oid) ,new OctetString("hello") ));
		//我想默认再追加一个
		pdu.add(new VariableBinding(new OID(".1.3.6.1.2.1.1.5.2") ,new OctetString("hello22") ));

		//返回结果 //todo 注意如果服务端接收到消息之后没有立即返回（如遇到断点，休眠等），客户端不会收到返回的数据  -- 这是因为客户端的超时时间决定的
		ResponseEvent event = snmp.send(pdu, target);
		System.out.println("sent");
		if(event!=null && event.getResponse()!=null) {
			Vector<? extends VariableBinding> bindings = event.getResponse().getVariableBindings();
			for(VariableBinding binding:bindings) {
				System.out.println(binding.getOid() + ":" + binding.getVariable());
			}
		}
	}

	public void close() throws IOException {
		snmp.close();
	}

	public static void main(String[] args) throws IOException {
		/**
         * v2c这个版本好像拿不到返回的数据，但是v3版本可以拿到服务器返回的数据
		 * 如果使用v3版本并且是GET请求可以拿到服务器返回的值，需要通过其他五个字段进行权限校验，如果校验失败，服务端不会放回response：success!
		 * 其他版本的任何请求形式好像都无法得到返回值
		 */
		SendTrapToAliyun2 client = new SendTrapToAliyun2("udp:47.100.122.116/9001", "test", "SHA", "testauth", "AES-128", "testpriv", SnmpConstants.version2c);
		client.send(".1.3.6.1.2.1.1.5.0");
		client.close();
	}
}
